iT邦幫忙

2026 iThome 鐵人賽

DAY 26
1

配合分支:day26/start,Repo: 按我

剩幾天就要結束了,趕緊把日記匯出來,怕真的動了真感情!這次就來做一個 class 方便把目前所有的日記做輸出吧。先不要把事情弄的太複雜,先轉成文字就好。

打通資料層的第一步

只需要將存在裝置儲存空間裡頭的資訊讀取出來後,取得日記的 Entity,就能夠用這些資訊組成這次需要的輸出資料。

https://ithelp.ithome.com.tw/upload/images/20260914/20141615OEd0tbg4dj.png

這個與存的日記有關係的 Class,從資料流的經驗來看,應該要放在 ViewModel 處理。一般來說,ViewModel 會作為「資料」和「畫面」的糨糊層,透過它溝通兩者。

這樣說起來,就需要將 DiaryExporter 注入到 ViewModel 之中,即要調整 PhotoDiaryViewModel 的建構子,和建構他的 Factory Class(ViewModel 是 Google 提供的 Library,需要用它提供的方法來建造)。

class PhotoDiaryViewModel(
    application: Application,
    private val diaryStore: DiaryStore,
    private val diaryExporter: DiaryExporter // 👈🏼 加入
)

接下來調整工廠:

https://ithelp.ithome.com.tw/upload/images/20260914/201416153i6QLV70C2.png

目前工廠在 MainActivity 生成,那邊也需要更新。

private val photoDiaryViewModel: PhotoDiaryViewModel by viewModels {
	val diaryStore = LocalDiaryStorage(applicationContext)
	// 👇🏼 多了這塊
	val diaryExporter = DiaryExporter(diaryStore)
	PhotoDiaryViewModelFactory(diaryStore, diaryExporter)
}

因為這專案也寫了測試,這邊也需要 inject 東西進去。請各位自行調整成:

val viewModel = PhotoDiaryViewModel(application(), diaryStore, DiaryExporter(diaryStore))

呼,改了個建構子沒想到要調整個地方這麼多!

ViewModel 需要提供什麼功能?

預計要做的事情就是顯示在一個 Dialog 上面就好(我們時間真的不多 =. =),故只需要提供「顯示在 Dialog 上的 output 就行了。」很簡單,很粗暴。

// 👇🏼 多一個 getter
var previewText by mutableStateOf<String?>(null)
	private set

提供這些方法即可:

fun previewSavedDiaries() {
	previewText = diaryExporter.export()
}

fun closePreview() {
	previewText = null
}

UI 更新

https://ithelp.ithome.com.tw/upload/images/20260914/20141615nL9MyO17lN.png

上面越來越擠啦!這次目標是加入一個「預覽已儲存日記」的按鈕。點擊之後一個 Dialog 跳出來,它會跟 previewText 取得現在日記的預覽。在列表上面加入按鈕,也就是調整 DiaryList

https://ithelp.ithome.com.tw/upload/images/20260914/20141615VJDEP3SLgH.png

當按鈕點擊後,去請 ViewModel 叫 exporter 開始上班。

onPreviewClick = { viewModel.previewSavedDiaries() }

接下來準備對話框,用 Android Jetpack Compose 內建的 AlertDialog 即可。

https://ithelp.ithome.com.tw/upload/images/20260914/20141615jgsymLJWsH.png

要怎麼樣才會顯示這個對話框呢?只要 previewText 不是空的就可以顯示。
加在最上面的 PhotoDiaryApp 區塊內:

val previewText = viewModel.previewText
if (previewText != null) {
	DiaryPreviewDialog(
		previewText = previewText,
		// 👇🏼 按下 Close 或是把對話框 dismiss 掉,就清空 previewText
		onClose = { viewModel.closePreview() }
	)
}

P.S:完整的 Repo 裡頭的程式碼也加上了測試,有興趣的讀者歡迎開啟 GitHub 查看。


配合分支:day26/plain-text-preview,Repo: 按我

想要 Markdown ⋯ 對吧?你的眼神已經出賣你了。就來強化這個部分,預計加入一個下拉選單選擇格式:純文字、Markdown。這邊做法都是老調重彈,掌握了 25 天進度,絕對難不倒你!

先加入 enum class:

enum class DiaryExportFormat(
    val displayName: String
) {
    PLAIN_TEXT("純文字"),
    MARKDOWN("Markdown")
}

有了這個之後,DiaryExporter 就可以對不同的格式做相對應的處理。

https://ithelp.ithome.com.tw/upload/images/20260914/20141615wJ0ixpU7xS.png

這邊操作和「修改心情」一樣,也需要在 ViewModel 提供狀態供使用者紀錄。

// getter
var selectedExportFormat by mutableStateOf(DiaryExportFormat.PLAIN_TEXT)  // 👀 預設純文字
	private set
	
// setter
fun updateExportFormat(format: DiaryExportFormat) {
	selectedExportFormat = format
}

// 要求 previewText 更新時,要傳入 Format
fun previewSavedDiaries() {
	previewText = diaryExporter.export(selectedExportFormat)
}

接著, 在 DiaryList 再加入一個下拉式選單:

https://ithelp.ithome.com.tw/upload/images/20260914/201416151ug6cUc0SQ.png

在 DiaryList 更新,最終接上 PhotoDiaryApp 這一段 Flow 就請讀者自行完成囉!

完成會像這個樣子:

TL;DR

  • ViewModel 糨糊層:ViewModel 是 data 與畫面之間的橋樑,一般來說會透過它進行兩者的溝通

上一篇
[Day25] 再次修復資料流的錯誤
下一篇
[Day27] 輸出格式,說來就來
系列文
我的第一個手作りAndroid App!快樂學習物件導向程式設計!28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
chiaominchang222
iT邦新手 4 級 ‧ 2026-09-14 20:47:22

有安迪 是不是今天發完就剩下四天了但我還沒發今天的QQ

該更新了吧

0
AndyAWD
iT邦研究生 5 級 ‧ 2026-09-14 23:45:11

剩四天啦!

這活動太硬了!!!

我要留言

立即登入留言